package com.kamistoat.meimeimall.meimeimallseckill.service.impl;

import com.kamistoat.meimeimall.common.utils.PageUtils;
import com.kamistoat.meimeimall.common.utils.Query;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.kamistoat.meimeimall.common.To.seckillTo.SecKillProductCacheTo;
import com.kamistoat.meimeimall.common.To.seckillTo.SecKillProductTo;
import com.kamistoat.meimeimall.common.To.seckillTo.SecKillSessionTo;
import com.kamistoat.meimeimall.common.To.seckillTo.SecKillSkuInfoTo;
import com.kamistoat.meimeimall.common.constant.SecKillConstant;
import com.kamistoat.meimeimall.common.dubboInterface.ProductDubboInterface;
import com.kamistoat.meimeimall.common.exception.BizCodeEnum;
import com.kamistoat.meimeimall.common.exception.RRException;
import com.kamistoat.meimeimall.common.utils.R;
import com.kamistoat.meimeimall.meimeimallseckill.entity.SeckillSkuRelationEntity;
import com.kamistoat.meimeimall.meimeimallseckill.feign.ProductFeignService;
import com.kamistoat.meimeimall.meimeimallseckill.service.SeckillSkuRelationService;
import com.kamistoat.meimeimall.meimeimallseckill.dao.SeckillSessionDao;
import com.kamistoat.meimeimall.meimeimallseckill.entity.SeckillSessionEntity;
import com.kamistoat.meimeimall.meimeimallseckill.service.SeckillSessionService;
import org.apache.dubbo.config.annotation.Reference;
import org.redisson.api.RLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("seckillSessionService")
public class SeckillSessionServiceImpl extends ServiceImpl<SeckillSessionDao, SeckillSessionEntity> implements SeckillSessionService {
    protected Logger logger = LoggerFactory.getLogger(SeckillSessionServiceImpl.class);

    @Autowired
    SeckillSkuRelationService seckillSkuRelationService;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    RabbitTemplate rabbitTemplate;

    @Reference
    ProductDubboInterface productDubboInterface;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<SeckillSessionEntity> page = this.page(
                new Query<SeckillSessionEntity>().getPage(params),
                new QueryWrapper<SeckillSessionEntity>()
        );
        return new PageUtils(page);
    }

    /**
     * 从数据库检查未来三天内的秒杀活动。
     * 将秒杀活动预热到缓存中，同时将秒杀商品也预热到缓存中。
     */
    @Override
    public void uploadThreeDaySecKill() {
        // 1. 从数据库查询三天内的秒杀活动
        List<SecKillSessionTo> sessionToList = getThreeDaySecKillSessionFormDatabase();
        // 2. 预热
        if (sessionToList != null && sessionToList.size() > 0) {
            for (SecKillSessionTo secKillSessionTo : sessionToList) {
                uploadSecKillSessionAndProduct(secKillSessionTo);
            }
        }
    }

    /**
     * 从数据库中获取未来三天的秒杀活动
     */
    @Override
    public List<SecKillSessionTo> getThreeDaySecKillSessionFormDatabase() {
        LocalDate nowTime = LocalDate.now();  // 直接用库方法获取当前日期
        LocalDate endDay = nowTime.plusDays(2);// 在这个时间的基础上加2天
        String endTime = endDay.toString() + " 23:59:59";  // 拼接上24点时刻

        // 可以直接使用wrapper.between()来进行条件查询。永远都是传递String类型数据，MySql会自动匹配格式
        List<SeckillSessionEntity> secKillSessionList = this.list(new QueryWrapper<SeckillSessionEntity>()
                .between("start_time", nowTime, endTime)
                .or(queryWrapper -> queryWrapper.between("end_time", nowTime, endTime))
                .eq("status", SecKillConstant.SecKillSessionStatus.SESSION_VALID.getCode())
                .orderByAsc("start_time"));

        // 构建 SecKillSessionTo
        if (secKillSessionList != null && secKillSessionList.size() > 0) {
            // 如果活动不是空的，就把这些Entity类转换成To类，顺便把每场活动对应的商品也查出来
            return buildSecKillSessionToList(secKillSessionList);
        }
        return null;
    }

    /**
     * 从数据库获取未来所有的秒杀活动
     */
    @Override
    public List<SecKillSessionTo> getAllSecKillSessionFormDatabase() {
        LocalDate nowTime = LocalDate.now();  // 直接用库方法获取当前日期

        // 可以直接使用wrapper.between()来进行条件查询。永远都是传递String类型数据，MySql会自动匹配格式
        List<SeckillSessionEntity> secKillSessionList = this.list(new QueryWrapper<SeckillSessionEntity>()
                .gt("start_time", nowTime)
                .or(queryWrapper -> queryWrapper.ge("end_time", nowTime))
                .eq("status", SecKillConstant.SecKillSessionStatus.SESSION_VALID.getCode())
                .orderByAsc("start_time"));

        // 构建 SecKillSessionTo
        if (secKillSessionList != null && secKillSessionList.size() > 0) {
            // 如果活动不是空的，就把这些Entity类转换成To类，顺便把每场活动对应的商品也查出来
            return buildSecKillSessionToList(secKillSessionList);
        }
        return null;
    }

    /**
     * 向数据库添加新的秒杀活动本体
     *
     * @param sessionEntity
     */
    @Override
    public void addSecKillSession(SeckillSessionEntity sessionEntity) {
        SeckillSessionEntity one = this.getOne(new QueryWrapper<SeckillSessionEntity>()
                .select("id", "name")
                .eq("start_time", sessionEntity.getStartTime())
                .eq("end_time", sessionEntity.getEndTime()));
        if (one != null) {
            throw new RRException(BizCodeEnum.SECKILL_ADD_SAME_SESSION.getMessage() + ":" + one.getName(), BizCodeEnum.SECKILL_ADD_SAME_SESSION.getCode());
        } else {
            sessionEntity.setStatus(SecKillConstant.SecKillSessionStatus.SESSION_VALID.getCode());
            this.save(sessionEntity);
        }
    }

    /**
     * 修改一个秒杀活动本体的信息，注意可能涉及到预热活动的修改
     *
     * @param sessionEntity
     */
    @Override
    public void updateSecKillSession(SeckillSessionEntity sessionEntity) {
        // 禁止修改一个正在进行的秒杀
        SecKillSessionTo currentSecKillSession = getCurrentSecKillSession();
        if (currentSecKillSession.getId().equals(sessionEntity.getId())) {
            throw new RRException(BizCodeEnum.FORBID_UPDATE_CURRENT_SESSION.getMessage(), BizCodeEnum.FORBID_UPDATE_CURRENT_SESSION.getCode());
        }
        // 保存修改
        SeckillSessionEntity oriSession = this.getById(sessionEntity.getId());
        this.updateById(sessionEntity);
        long oriStarTime = oriSession.getStartTime().getTime();
        long oriEndTime = oriSession.getEndTime().getTime();
        long newStartTime = sessionEntity.getStartTime().getTime();
        long newEndTime = sessionEntity.getEndTime().getTime();
        long pivot = new Date(LocalDate.now().plusDays(2).toString() + " 23:59:59").getTime();
        // 如果修改了时间到3天内，则直接执行预热。不管之前是否预热过，这里总会覆盖
        if (newStartTime < pivot || newEndTime < pivot) {
            List<SecKillSessionTo> tos = buildSecKillSessionToList(Collections.singletonList(sessionEntity));
            uploadSecKillSessionAndProduct(tos.get(0));
        }
        // 否则就执行旧缓存清除。不管之前是否预热过，这里总会清除
        else {
            deleteSessionsFormRedis(oriSession.getId() + ":" + oriStarTime + "_" + oriEndTime);

        }
    }

    /**
     * 为秒杀活动绑定商品。
     * 如果秒杀活动在缓存中，则商品同样预热。
     * 如果秒杀活动不在缓存中，则不需要预热。
     *
     * @param secKillProductToList
     */
    @Override
    @Transactional
    public void boundSessionProduct(List<SecKillProductTo> secKillProductToList) {
        List<SeckillSkuRelationEntity> relationList = secKillProductToList.stream().map(secKillProductTo -> {
            SeckillSkuRelationEntity seckillSkuRelationEntity = new SeckillSkuRelationEntity();
            BeanUtils.copyProperties(secKillProductTo, seckillSkuRelationEntity);
            return seckillSkuRelationEntity;
        }).collect(Collectors.toList());
        // 先入数据库
        seckillSkuRelationService.saveBatch(relationList);
        // 查询所属秒杀信息
        SeckillSessionEntity secKillSession = this.getById(secKillProductToList.get(0).getSessionId());
        // 缓存处理
        judgeSecKillSessionNeedReUpload(secKillSession);
    }

    /**
     * 为秒杀活动修改商品。
     * 同样的，如果活动在缓存中，同时重新预热
     *
     * @param secKillProductToList
     */
    @Override
    @Transactional
    public void updateSessionProduct(List<SecKillProductTo> secKillProductToList) {
        List<SeckillSkuRelationEntity> relationList = secKillProductToList.stream().map(secKillProductTo -> {
            SeckillSkuRelationEntity relationEntity = new SeckillSkuRelationEntity();
            BeanUtils.copyProperties(secKillProductTo, relationEntity);
            return relationEntity;
        }).collect(Collectors.toList());
        seckillSkuRelationService.updateBatchById(relationList);
        // 查询所属秒杀信息
        SeckillSessionEntity secKillSession = this.getById(secKillProductToList.get(0).getSessionId());
        // 缓存处理
        judgeSecKillSessionNeedReUpload(secKillSession);
    }

    /**
     * 判断一个秒杀实体类是否已经被预热。如果是，则对其执行重新预热
     */
    private void judgeSecKillSessionNeedReUpload(SeckillSessionEntity sessionEntity) {
        if (stringRedisTemplate.hasKey(
                SecKillConstant.SEC_SESSION_PREFIX + sessionEntity.getId() + ":" + sessionEntity.getStartTime().getTime() + "_" + sessionEntity.getEndTime().getTime())) {
            // 如果秒杀本身已经预热，那直接执行根据数据库预热秒杀即可. 否则啥也不用管
            uploadSecKillSessionAndProduct(buildSecKillSessionToList(Collections.singletonList(sessionEntity)).get(0));
        }
    }

    /**
     * 根据查询出的秒杀活动制作SecKillSessionTo
     */
    private List<SecKillSessionTo> buildSecKillSessionToList(List<SeckillSessionEntity> secKillSessionList) {
        return secKillSessionList.stream().map(seckillSessionEntity -> {
            SecKillSessionTo secKillSessionTo = new SecKillSessionTo();
            // 拷贝Entity到To
            BeanUtils.copyProperties(seckillSessionEntity, secKillSessionTo);
            // 查询对应的商品
            List<SeckillSkuRelationEntity> sessionRelationList =
                    seckillSkuRelationService.list(
                            new QueryWrapper<SeckillSkuRelationEntity>().
                                    eq("session_id", seckillSessionEntity.getId()));
            // 把SeckillSkuRelationEntity类转变为SecKillProductTo类
            if (sessionRelationList == null || sessionRelationList.size() == 0) {
                secKillSessionTo.setSecKillProductCacheToList(null);
            } else {
                List<SecKillProductCacheTo> productCacheToList = sessionRelationList.stream().map(
                        seckillSkuRelationEntity -> {
                            SecKillProductCacheTo cacheTo = new SecKillProductCacheTo();
                            cacheTo.setStartTime(secKillSessionTo.getStartTime().getTime());
                            cacheTo.setEndTime(secKillSessionTo.getEndTime().getTime());
                            SecKillProductTo secKillProductTo = new SecKillProductTo();
                            BeanUtils.copyProperties(seckillSkuRelationEntity, secKillProductTo);
                            R skuInfoR = productDubboInterface.getSkuInfoForSecKill(seckillSkuRelationEntity.getSkuId());
                            if (skuInfoR.getCode() == 0) {
                                cacheTo.setSecKillSkuInfoTo(JSON.parseObject((String) skuInfoR.get("secKillSkuInfoTo"), SecKillSkuInfoTo.class));
                            }
                            return cacheTo;
                        }).collect(Collectors.toList());
                secKillSessionTo.setSecKillProductCacheToList(productCacheToList);
            }
            return secKillSessionTo;
        }).collect(Collectors.toList());
    }

    /**
     * 将一个秒杀活动预热到缓存中。包括三个部分:
     * 1. 活动本身
     * 2. 商品Map
     * 3. 商品信号锁
     *
     * @param secKillSessionTo
     */
    private void uploadSecKillSessionAndProduct(SecKillSessionTo secKillSessionTo) {
        // 将起始时间_结束时间作为 键
        // Date类型的数据可以使用.getTime()将数据转换成距离1970年毫秒数值的Long类型，方便比较
        long startTime = secKillSessionTo.getStartTime().getTime();
        long endTime = secKillSessionTo.getEndTime().getTime();

        /**
         * 秒杀活动本体的缓存的Key为 seckill:session:秒杀Id:开始时间_结束时间
         */
        if (startTime > new Date().getTime()) {
            String key = SecKillConstant.SEC_SESSION_PREFIX + secKillSessionTo.getId() + ":" + startTime + "_" + endTime;
            // 无论该活动是否已经预热过，都执行预热，做冗余保护
            stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(secKillSessionTo));
        }

        /**
         * 秒杀活动商品预热
         */
        if (secKillSessionTo.getSecKillProductCacheToList() != null &&
                secKillSessionTo.getSecKillProductCacheToList().size() > 0 &&
                secKillSessionTo.getStartTime().getTime() > new Date().getTime()) {
            for (SecKillProductCacheTo secKillProductCacheTo : secKillSessionTo.getSecKillProductCacheToList()) {
                secKillProductCacheTo.setRandomCode(UUID.randomUUID().toString().replace("-", ""));
                SecKillSkuInfoTo oriInfo = secKillProductCacheTo.getSecKillSkuInfoTo();
                SecKillProductTo secInfo = secKillProductCacheTo.getSecKillProductTo();

                // 入两个缓存
                String semaphoreKey = SecKillConstant.SEC_PRODUCT_SEMAPHORE + secKillProductCacheTo.getRandomCode();
                if (!stringRedisTemplate.hasKey(semaphoreKey)) {
                    RSemaphore semaphore = redissonClient.getSemaphore(semaphoreKey);
                    // 信号量的值就是当前秒杀商品的备货量
                    semaphore.trySetPermits(secInfo.getSeckillCount());
                    BoundHashOperations<String, String, String> redisMapOps =
                            stringRedisTemplate.boundHashOps(SecKillConstant.SEC_PRODUCT_MAP_PREFIX + secInfo.getSkuId());
                    redisMapOps.put(secInfo.getSessionId().toString(), JSON.toJSONString(secKillProductCacheTo));
                }
            }
        }
    }

    /**
     * 带缓查询当前时间段的秒杀活动。如果当前时间段没有秒杀活动则显示下一场最近的秒杀活动。
     * 特殊缓存逻辑，因为过期时间需要动态确定，无法使用自定义注解。
     */
    @Override
    public SecKillSessionTo getCurrentSecKillSession() {
        String lockName = SecKillConstant.CURRENT_SEC_SESSION_PREFIX + "redis:through:lock";
        while (true) {
            String currentSessionString = stringRedisTemplate.opsForValue().get(SecKillConstant.CURRENT_SEC_SESSION_PREFIX);
            if (!StringUtils.isEmpty(currentSessionString)) {
                SecKillSessionTo secKillSessionTo = JSON.parseObject(currentSessionString, SecKillSessionTo.class);
                logger.info("命中缓存:" + SecKillConstant.CURRENT_SEC_SESSION_PREFIX);
                return secKillSessionTo;
            }

            RLock searchLock = redissonClient.getLock(lockName);
            try {
                boolean hasLock = searchLock.tryLock();
                if (hasLock) {
                    logger.info("放行缓存构建");
                    List<SecKillSessionTo> futureSecKillSessionList = getFutureSecKillSessionList();
                    if (futureSecKillSessionList != null && futureSecKillSessionList.size() > 0) {
                        SecKillSessionTo secKillSessionTo = futureSecKillSessionList.get(0);
                        // 缓存的过期时间就是到秒杀活动结束为止
                        stringRedisTemplate.opsForValue().set(
                                SecKillConstant.CURRENT_SEC_SESSION_PREFIX, JSON.toJSONString(secKillSessionTo),
                                secKillSessionTo.getEndTime().getTime() - new Date().getTime(), TimeUnit.MILLISECONDS);
                        return secKillSessionTo;
                    } else {
                        SecKillSessionTo nullPoint = new SecKillSessionTo();
                        stringRedisTemplate.opsForValue().set(
                                SecKillConstant.CURRENT_SEC_SESSION_PREFIX, JSON.toJSONString(nullPoint));
                    }
                } else {
                    Thread.sleep(200);
                }
            } catch (Exception exception) {
                throw new RRException(exception.getMessage(), BizCodeEnum.UNKNOW_EXCEPTION.getCode());
            } finally {
                searchLock.unlock();
            }
        }
    }

    /**
     * 带缓查询三天内的秒杀活动，只返回正在进行和还未开始的。
     */
    @Override
    public List<SecKillSessionTo> getFutureSecKillSessionList() {
        // 制作优先队列
        PriorityQueue<SecKillSessionTo> futureSecKillSessionToQueue =
                new PriorityQueue<SecKillSessionTo>(Comparator.comparing(SecKillSessionTo::getStartTime));

        /**
         * 获取缓存中早已预热的 List<SecKillSessionTo>
         */
        long curtime = new Date().getTime();
        // redis毕竟不是ES，所以只能是先把所有的秒杀场次都查出来，然后筛选出未来时间的场次
        // 用的是正则表达式，查询出所有与seckill:sessions:*匹配的redis键名。注意只是键，不是键值对
        Set<String> sessionKeySet = stringRedisTemplate.keys(SecKillConstant.SEC_SESSION_PREFIX + "*");

        // 说明未来三天内压根就没有秒杀活动
        if (sessionKeySet == null || sessionKeySet.isEmpty()) {
            return null;
        }

        /**
         * 对每一场活动，判断活动的结束时间是否比现在晚
         */
        for (String sessionKey : sessionKeySet) {
            // seckill:session:13:1638961200000_1638964800000
            String sessionName = sessionKey.replace(SecKillConstant.SEC_SESSION_PREFIX, "");
            long sessionId = Long.parseLong(sessionName.split(":")[0]);
            long sessionStartTime = Long.parseLong(sessionName.split(":")[0].split("_")[0]);
            long sessionEndtime = Long.parseLong(sessionName.split(":")[0].split("_")[1]);

            /**
             * 只要结束时间比现在晚，那就是一场未来的秒杀活动，添加到优先队列中按照开始时间排列。
             */
            if (curtime <= sessionEndtime) {
                String secKillSessionToString = stringRedisTemplate.opsForValue().get(sessionKey);
                futureSecKillSessionToQueue.add(JSON.parseObject(secKillSessionToString, SecKillSessionTo.class));
            }
            /**
             * 否则直接在这里执行过期秒杀的删除
             */
            else {
                deleteSessionsFormRedis(sessionKey);
            }
        }

        List<SecKillSessionTo> futureSecKillSessionList = new ArrayList<>();
        while (!futureSecKillSessionToQueue.isEmpty()) {
            SecKillSessionTo secKillSessionTo = futureSecKillSessionToQueue.poll();
            /**
             * 删除秒杀随机码
             */
            secKillSessionTo.getSecKillProductCacheToList().forEach(secKillProductCacheTo -> secKillProductCacheTo.setRandomCode(""));
            futureSecKillSessionList.add(secKillSessionTo);
        }
        return futureSecKillSessionList;
    }

    /**
     * 带缓查询一个商品是否正在进行秒杀。或者未来三天内存在秒杀活动。直接从已经预热的秒杀商品缓存中查询
     *
     * @param skuId skuId
     */
    @Override
    public SecKillProductCacheTo getSkuInSession(Long skuId) {
        Boolean hasKey = stringRedisTemplate.hasKey(SecKillConstant.SEC_PRODUCT_MAP_PREFIX + skuId);
        if (hasKey) {
            PriorityQueue<SecKillProductCacheTo> futureProductQueue =
                    new PriorityQueue<SecKillProductCacheTo>(Comparator.comparing(SecKillProductCacheTo::getStartTime));
            List<Object> values = stringRedisTemplate.opsForHash().values(SecKillConstant.SEC_PRODUCT_MAP_PREFIX + skuId);
            for (Object value : values) {
                futureProductQueue.offer(JSON.parseObject((String) value, SecKillProductCacheTo.class));
            }
            SecKillProductCacheTo nearSecKillProduct = futureProductQueue.poll();
            if (nearSecKillProduct != null && nearSecKillProduct.getStartTime() > new Date().getTime()) {
                nearSecKillProduct.setRandomCode("");
            }
            return nearSecKillProduct;
        } else {
            return null;
        }
    }

    /**
     * 从缓存中删除已经过期一天的促销预热
     */
    @Override
    public void deleteOneDaySecKill() {
        // 1 查出所有活动
        long curtime = new Date().getTime();
        // redis毕竟不是ES，所以只能是先把所有的秒杀场次都查出来，然后筛选出包含当前时间的场次
        // 用的是正则表达式，查询出所有与seckill:sessions:*匹配的redis键名。注意只是键，不是键值对
        Set<String> sessionKeySet = stringRedisTemplate.keys(SecKillConstant.SEC_SESSION_PREFIX + "*");

        // 对每一场活动，判断活动的结束时间是否比 今天-1天 还早
        for (String sessionKey : sessionKeySet) {
            // seckill:sessions:1638961200000_1638964800000
            String sessionTime = sessionKey.replace(SecKillConstant.SEC_SESSION_PREFIX, "");
            Long sessionEndtime = Long.parseLong(sessionTime.split("_")[1]);
            if (curtime - 1000 * 60 * 60 * 24 >= sessionEndtime) {
                // 2 如果找到了已经过期一天的活动，则将该促销活动从缓存中删除
                deleteSessionsFormRedis(sessionKey);
            }
        }
    }

    /**
     * 终止秒杀活动。同时从redis中删除可能存在的预热数据
     */
    @Override
    public void terminateSessions(List<String> redisSessionKeyList) {
        for (String sessionKey : redisSessionKeyList) {
            logger.info("中止秒杀活动:" + sessionKey);
            long sessionId = Long.parseLong(sessionKey.split(":")[0]);
            this.update(new UpdateWrapper<SeckillSessionEntity>().eq("id", sessionId).set("status", SecKillConstant.SecKillSessionStatus.SESSION_TERMINATE.getCode()));
            deleteSessionsFormRedis(sessionKey);
        }
    }

    /**
     * 从缓存中删除某个促销活动对应的促销商品缓存
     */
    private void deleteSessionsFormRedis(String sessionKey) {
        String sessionToString = stringRedisTemplate.opsForValue().get(SecKillConstant.SEC_SESSION_PREFIX + sessionKey);
        if (!StringUtils.isEmpty(sessionToString)) {
            logger.info("删除促销活动预热 " + sessionKey);
            SecKillSessionTo secKillSessionTo = JSON.parseObject(sessionToString, SecKillSessionTo.class);
            for (SecKillProductCacheTo cacheTo : secKillSessionTo.getSecKillProductCacheToList()) {
                // 删活动
                stringRedisTemplate.delete(SecKillConstant.SEC_SESSION_PREFIX + sessionKey);
                // 删信号量锁
                stringRedisTemplate.delete(SecKillConstant.SEC_PRODUCT_SEMAPHORE + cacheTo.getRandomCode());
                // 删商品
                stringRedisTemplate.opsForHash().delete(SecKillConstant.SEC_PRODUCT_MAP_PREFIX + cacheTo.getSecKillProductTo().getSkuId(), cacheTo.getSecKillProductTo().getSessionId());
            }
        }
    }

    /**
     * 强制删除缓存中所有的秒杀信息
     */
    @Override
    public void clearAllSecKillSession() {
        Set<String> keys = stringRedisTemplate.keys("seckill:*");
        if (keys != null && keys.size() > 0) {
            stringRedisTemplate.delete(keys);
        }
    }

    /**
     * 前端在秒杀商品页面上直接点击 立刻购买，该方法只负责检验信号量
     *
     * @param sessionId    秒杀场次Id
     * @param randomCode   秒杀商品的随机码
     * @param skuId        商品skuId，用于和场次-商品关联Id做联合检查
     * @param num          要购买多少件商品
     * @return R对象。如果成功就把订单号放进去
     */
    @Override
    public void secKillProduct(Long sessionId, String randomCode, Long skuId, Integer num) {
        /**
         * 直接检验信号量锁的可用余额是否大于0，如果大于0就放过请求
         */
        RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstant.SEC_PRODUCT_SEMAPHORE + randomCode);
        if (semaphore == null || semaphore.availablePermits() <= 0) {
            throw new RRException(BizCodeEnum.SECKILL_VERIFY_NO_SEMAPHORE.getMessage(), BizCodeEnum.SECKILL_VERIFY_NO_SEMAPHORE.getCode());
        }
    }

    /**
     * 检查secKillSession，不允许创建重复时间内的session
     *
     * @param seckillSession
     */
    @Override
    public R checkSeckillSession(SeckillSessionEntity seckillSession) {
        Date startTime = seckillSession.getStartTime();
        Date endTime = seckillSession.getEndTime();
        List<Long> clashSessionList = baseMapper.getClashSession(startTime, endTime);
        if (!clashSessionList.isEmpty() || clashSessionList.size() > 0) {
            String msg = "";
            for (Long aLong : clashSessionList) {
                msg = msg + aLong.toString() + "/";
            }
            return R.error(1, "该秒杀时间与" + msg + "号秒杀活动冲突");
        } else {
            return R.ok();
        }
    }
}